#include <Common/util.h>
#include <VM/chunk.h>
#include <VM/object.h>
#include <string>
#include <fstream>
#include <cstdio>
#include <yas/std_types.hpp>
#include <yas/serialize.hpp>

void Chunk::write(u8_t byte, int line)
{
	code.push_back(byte);
	lines.push_back(line);
}

void Chunk::write(OpCode opcode, int line)
{
	write(static_cast<u8_t>(opcode), line);
}

size_t Chunk::addConstant(Value val)
{
	constants.push_back(val);
	return constants.size() - 1;
}

void Chunk::disassemble(const char *name)
{
	print("== %s ==\n", name);

	for (size_t i = 0; i < code.size();)
	{
		i = disassembleInstruction(i);
	}
}

static int simpleInstruction(const char *name, int offset)
{
	print("%s\n", name);
	return offset + 1;
}

static int constantInstruction(const char *name, const Chunk &chunk, int offset)
{
	auto constant = chunk.getCode(offset + 1);
	printf("%-18s %4d '", name, constant);
	std::cout << chunk.getConstant(constant);
	print("'\n");
	return offset + 2;
}

static int byteInstruction(const char *name, const Chunk &chunk, int offset)
{
	u8_t slot = chunk.getCode(offset + 1);
	printf("%-18s %4d\n", name, slot);
	return offset + 2;
}

static int jumpInstruction(const char *name, int sign, const Chunk &chunk, int offset)
{
	uint16_t jump = static_cast<uint16_t>(chunk.getCode(offset + 1) << 8);
	jump |= chunk.getCode(offset + 2);
	printf("%-18s %4d -> %d\n", name, offset, offset + 3 + sign * jump);
	return offset + 3;
}

int Chunk::disassembleInstruction(int offset)
{
	// print不支持宽度格式化
	printf("%04d ", offset);

	// 打印字节码对应的源代码所在行
	if (offset > 0 && lines[offset] == lines[offset - 1])
	{
		print("   | "); // 与上一句为同行代码
	}
	else
	{
		printf("%4d ", lines[offset]); // 打印行数
	}

	// 打印字节码信息
	OpCode instruction = OpCode(code[offset]);
	switch (instruction)
	{
	case OpCode::CONSTANT:
		return constantInstruction("OP_CONSTANT", *this, offset);
	case OpCode::NIL:
		return simpleInstruction("OP_NIL", offset);
	case OpCode::TRUE:
		return simpleInstruction("OP_TRUE", offset);
	case OpCode::FALSE:
		return simpleInstruction("OP_FALSE", offset);
	case OpCode::ADD:
		return simpleInstruction("OP_ADD", offset);
	case OpCode::SUBTRACT:
		return simpleInstruction("OP_SUB", offset);
	case OpCode::MULTIPLY:
		return simpleInstruction("OP_MUL", offset);
	case OpCode::DIVIDE:
		return simpleInstruction("OP_DIV", offset);
	case OpCode::NEGATE:
		return simpleInstruction("OP_NEGATE", offset);
	case OpCode::NOT:
		return simpleInstruction("OP_NOT", offset);
	case OpCode::EQEQ:
		return simpleInstruction("OP_EQUAL", offset);
	case OpCode::NEQ:
		return simpleInstruction("OP_NOT_EQUAL", offset);
	case OpCode::GT:
		return simpleInstruction("OP_GREATER", offset);
	case OpCode::GTE:
		return simpleInstruction("OP_GREATER_EQUAL", offset);
	case OpCode::LT:
		return simpleInstruction("OP_LESS", offset);
	case OpCode::LTE:
		return simpleInstruction("OP_LESS_EQUAL", offset);
	case OpCode::POP:
		return simpleInstruction("OP_POP", offset);
	case OpCode::DEFINE_GLOBAL:
		return simpleInstruction("OP_DEFINE_GLOBAL", offset);
	case OpCode::GET_GLOBAL:
		return constantInstruction("OP_GET_GLOBAL", *this, offset);
	case OpCode::SET_GLOBAL:
		return constantInstruction("OP_SET_GLOBAL", *this, offset);
	case OpCode::GET_LOCAL:
		return byteInstruction("OP_GET_LOCAL", *this, offset);
	case OpCode::SET_LOCAL:
		return byteInstruction("OP_SET_LOCAL", *this, offset);
	case OpCode::JUMP:
		return jumpInstruction("OP_JUMP", 1, *this, offset);
	case OpCode::JUMP_IF_TRUE:
		return jumpInstruction("OP_JUMP_IF_TRUE", 1, *this, offset);
	case OpCode::JUMP_IF_FALSE:
		return jumpInstruction("OP_JUMP_IF_FALSE", 1, *this, offset);
	case OpCode::LOOP:
		return jumpInstruction("OP_LOOP", -1, *this, offset);
	case OpCode::CALL:
		return byteInstruction("OP_CALL", *this, offset);
	case OpCode::RETURN:
		return simpleInstruction("OP_RETURN", offset);
	default:
		print("Unknown opcode %d\n", static_cast<u8_t>(instruction));
		return offset + 1;
	}
}

void Chunk::serialize(const char *fname)
{
	// std::monostate support was hand-written
	// FIXME 不支持自定义类（FunctionObject）
	const auto oo = YAS_OBJECT_NVP("Chunk",
								   ("code", code),
								   // ("constants", constants),
								   ("lines", lines));

	std::remove(fname);

	yas::file_ostream os(fname);
	yas::save<yas::file | yas::binary>(os, oo);
	os.flush();
}

void Chunk::deserialize(const char *fname)
{
	code.clear();
	constants.clear();
	lines.clear();

	try
	{
		yas::file_istream is(fname);
		yas::load<yas::file | yas::binary>(is,
										   YAS_OBJECT_NVP("Chunk",
														  ("code", code),
														  // ("constants", constants),
														  ("lines", lines)));
	}
	catch (const yas::io_exception &e)
	{
		print("Read Cache Error: Can't open file %s\n", fname);
	}
}
